#include "FbxJsonExporter.h"

namespace GC
{
	using namespace GC;
	
	FbxJsonExporter::FbxJsonExporter(const bool a_prettify)
		: m_prettify(a_prettify)
	{		
		m_tabCount = 0;
		m_output = FbxString("");
	}
	
	/// <summary>
	/// Exports all the data into the json file
	/// Invokes all the other ExportPart...
	/// </summary>
	/// <param name="a_data">The data to export</param>
	/// <param name="a_outputName">Name of the json output file</param>
	/// <returns></returns>
	const bool FbxJsonExporter::Export(const FbxData* a_data, const _TCHAR* a_outputName)
	{
		//Table of Contents
		/*
		How it loops through it to print out the data
		Table of contents:

		METADATA
			meshCount
			skeletonCount
			animationCount
			framerate
			dualquaternions
		MESHES
			meshdata
				id
				name				
				verticesCount
				facesCount
				verticesPerFace
				uvChannelCount
				boneCount			
			localTransform
			vertices
			verticeIndex
			uvchannels
			normals
			bones
				bonedata
					id
					name
					parentID
					parentName
					linkIndicesCount
				linkIndices
				weightValues
				localTransform
				bindTransform
		SKELETONS			
			id
			name
			parentID
			parentName					
			localTransform

		ANIMATIONS
			animationdata
				name
				layerCount
				animationLength
			layers
				nodecount
				nodes
					nodedata
						id
						name
						parentID
						parentName						
					channels
						transform
							keyCount
							keys
								frame
								frameValue
						scale
							x,y,z
								keyCount
								keys
									frame
									frameValue
		*/
		//Create JSON start
		#if defined (_DEBUG)
			Cout << _T("Exporting JSON") << std::endl;
		#endif

		std::wofstream f_filestream;
		f_filestream.open(a_outputName);
		try
		{
			if (!f_filestream.is_open()) return false;

			//Start the JSON file
			m_output += "{";
			IncreaseTabCount();

			PrintOutput(f_filestream);
			//Output the metadata
			ExportMetadata(a_data, f_filestream);
			//Output the meshes and bones
			ExportMeshes(a_data, f_filestream);
			//Output the skeletal data
			ExportSkeletons(a_data, f_filestream);
			//Output animations
			ExportAnimations(a_data, f_filestream);
			//Close the JSON file		
			CloseKey(KeyContainerType::Braces);
			PrintOutput(f_filestream);
		}
		catch (int e)
		{
			f_filestream.close();
			return false;
		}	

		f_filestream.close();		

		return true;
	}

	void FbxJsonExporter::ExportMetadata(const FbxData* a_data, std::wofstream& a_filestream)
	{		
		#if defined (_DEBUG)
			Cout << _T("Exporting Metadata") << std::endl;
		#endif
		
		OpenKey("metadata", KeyContainerType::Braces);		

		AddKeyValuePair<const int>("meshCount", a_data->GetMeshesSize() );
		AddKeyValuePair<const int>("skeletonCount", a_data->GetSkeletonsSize() );
		AddKeyValuePair<const int>("animationCount", a_data->GetAnimationsSize() );
		AddKeyValuePair<const double>("framerate", a_data->GetFrameRate());
		//Will be 0 or 1 
		AddKeyValuePair<const bool>("dualquaternions", ConverterApp::GetSettings()->IsUsingDualQuaternions(), false, false);
			
		CloseKey(KeyContainerType::Braces, true);		

		PrintOutput(a_filestream);
	}
	
	void FbxJsonExporter::ExportMeshes(const FbxData* a_data, std::wofstream& a_filestream)
	{		
		#if defined (_DEBUG)
			Cout << _T("Exporting Meshes") << std::endl;
		#endif	
		
		OpenKey("meshes", KeyContainerType::Brackets);	
		
		const int f_meshCount = a_data->GetMeshesSize();

		for (int i = 0; i < f_meshCount; ++i)
		{
			ExportMesh(a_data->GetMeshes()[i].get());				

			if (i != f_meshCount - 1)
			{
				m_output += ",";
			}
		}
				
		CloseKey(KeyContainerType::Brackets, a_data->GetSkeletonsSize() > 0 || a_data->GetAnimationsSize() > 0);	

		PrintOutput(a_filestream);
	}

	void FbxJsonExporter::ExportMesh(const GC::FbxMesh* a_mesh)
	{
		const int f_uvChannelCount = a_mesh->GetUvChannelsSize();
		const int f_boneCount = a_mesh->GetBonesSize();
		//faces * vertPerFace
		const int f_totalVertices = a_mesh->GetVerticesIndexSize();
		const int f_normalsCount = a_mesh->GetNormalsSize();

		const bool f_hasNormals = f_normalsCount > 0;
		const bool f_hasUvs = f_uvChannelCount > 0;
		const bool f_hasBones = f_boneCount > 0;
		

		OpenKey(nullptr, KeyContainerType::Braces);
		
		OpenKey("meshdata", KeyContainerType::Braces);		

		AddKeyValuePair<const int>("id", a_mesh->GetID());
		AddKeyValuePair<const FbxString>("name", a_mesh->GetName(), true);		
		AddKeyValuePair<const int>("verticesCount", a_mesh->GetVerticesSize() );
		AddKeyValuePair<const int>("verticesPerFace", a_mesh->GetVerticesPerFace() );
		AddKeyValuePair<const int>("uvChannelCount", f_uvChannelCount);
		AddKeyValuePair<const int>("boneCount", f_boneCount, false, false);

		/* End of mesh data*/	
		CloseKey(KeyContainerType::Braces, true);
		
		AddKeyValuePairArray<VectorConstDouble>("localTransform", a_mesh->GetTransform(), FbxHelper::GetDQorMatrix4x4Length());
		AddKeyValuePairArray<VectorConstDouble>("vertices", a_mesh->GetVertices(), a_mesh->GetVerticesSize() );
		//Add the comma so it's verticeIndex [ ... ],  if boneCount is more than 0
		AddKeyValuePairArray<VectorConstInt>("verticeIndex", a_mesh->GetVerticesIndex(), f_totalVertices, f_hasNormals || f_hasUvs || f_hasBones);

		if (f_hasNormals)
		{
			AddKeyValuePairArray<VectorConstDouble>("normals", a_mesh->GetNormals(), f_normalsCount, f_hasUvs || f_hasBones);
		}

		if (f_hasUvs)
		{
			OpenKey("uvchannels", Brackets);
			for (int i = 0; i < f_uvChannelCount; ++i)
			{			
				AddKeyValuePairArray<VectorConstDouble>(nullptr, a_mesh->GetUVCoords()[i], a_mesh->GetUVCoords()[i].size(), i != f_uvChannelCount - 1);
			}

			CloseKey(Brackets, f_hasBones);
		}

		/* BONES FOR THIS MESH */
		if (f_hasBones)
		{
			ExportBones(a_mesh);
		}

		/* A MESH FINISHED */		
		CloseKey(KeyContainerType::Braces);
	}

	void FbxJsonExporter::ExportBones(const GC::FbxMesh* a_mesh)
	{		
		OpenKey("bones", KeyContainerType::Brackets);		

		const int f_boneCount = a_mesh->GetBonesSize();
		const int f_lastBoneIndex = f_boneCount - 1;

		for (int i = 0; i < f_boneCount; ++i)
		{	
			ExportBone(a_mesh->GetBones()[i].get(), i != f_lastBoneIndex);
		}

		CloseKey(KeyContainerType::Brackets);
	}

	void FbxJsonExporter::ExportBone(const FbxBone* a_bone, const bool a_addComma)
	{		
		const int f_linkIndicesCount = a_bone->GetLinkIndicesSize();
		OpenKey(nullptr, KeyContainerType::Braces);
		
		OpenKey("bonedata", KeyContainerType::Braces);

		AddKeyValuePair<const int>("id", a_bone->GetID());
		AddKeyValuePair<const FbxString>("name", a_bone->GetName(), true);
		//If a parent bone exists
		if (a_bone->GetParentID() != -1)
		{
			AddKeyValuePair<const int>("parentID", a_bone->GetParentID());
			AddKeyValuePair<const FbxString>("parentName", a_bone->GetParentName(), true);
		}

		AddKeyValuePair<const int>("linkIndicesCount", f_linkIndicesCount, false, false);

		//tabcount :6
		CloseKey(KeyContainerType::Braces, true);

		AddKeyValuePairArray<VectorConstInt>("linkIndices", a_bone->GetLinkIndices(), f_linkIndicesCount);
		AddKeyValuePairArray<VectorConstDouble>("weightValues", a_bone->GetWeightValues(), f_linkIndicesCount);

		AddKeyValuePairArray<VectorConstDouble>("localTransform", a_bone->GetTransform(), FbxHelper::GetDQorMatrix4x4Length());
		AddKeyValuePairArray<VectorConstDouble>("bindTransform", a_bone->GetBindTransform(), FbxHelper::GetDQorMatrix4x4Length(), false);
		
		CloseKey(KeyContainerType::Braces, a_addComma);
	}

	void FbxJsonExporter::ExportSkeletons(const FbxData* a_data, std::wofstream& a_filestream)
	{		
		#if defined (_DEBUG)
			Cout << _T("Exporting Skeletons") << std::endl;
		#endif
		const size_t f_skeletonSize = a_data->GetSkeletonsSize();
		OpenKey("skeletons", KeyContainerType::Brackets);
		
		for (size_t i = 0; i < f_skeletonSize; ++i)
		{
			ExportSkeleton(a_data->GetSkeletons()[i].get(), i != f_skeletonSize - 1);
		}

		//Add comma if data has animations
		CloseKey(KeyContainerType::Brackets, a_data->GetAnimationsSize() > 0);

		PrintOutput(a_filestream);
	}

	void FbxJsonExporter::ExportSkeleton(const FbxSkeleton* a_skeleton, const bool a_addComma)
	{
		OpenKey(nullptr, KeyContainerType::Braces);		

		AddKeyValuePair<const int>("id",a_skeleton->GetID());
		AddKeyValuePair<const FbxString>("name", a_skeleton->GetName(), true);
		//Check if parent exists
		if (a_skeleton->GetParentID() != -1)
		{
			AddKeyValuePair<const int>("parentID", a_skeleton->GetParentID());
			AddKeyValuePair<const FbxString>("parentName", a_skeleton->GetParentName(), true);
		}

		AddKeyValuePairArray<VectorConstDouble>("localTransform", a_skeleton->GetTransform(), FbxHelper::GetDQorMatrix4x4Length(), false);

		CloseKey(KeyContainerType::Braces, a_addComma);
	}

	void FbxJsonExporter::ExportAnimations(const FbxData* a_data, std::wofstream& a_filestream)
	{				
		#if defined (_DEBUG)
			Cout << _T("Exporting Animations") << std::endl;
		#endif

		OpenKey("animations", KeyContainerType::Brackets);
		const size_t f_animationSize = a_data->GetAnimationsSize();

		for (size_t i = 0; i < f_animationSize; ++i)
		{
			ExportAnimation(a_data->GetAnimations()[i].get(), i != f_animationSize - 1);
		}

		//Don't add comma since animations is last type
		CloseKey(KeyContainerType::Brackets);
		//Not doing PrintOutput because it happens basicly 2 lines of code after this functin ends!
	}

	/// <summary>
	/// Exports an individual Animation
	/// </summary>
	/// <param name="a_animation">The a_animation to export</param>
	/// <param name="a_addComma">The if adding comma or not</param>
	void FbxJsonExporter::ExportAnimation(const FbxAnimation* a_animation, const bool a_addComma)
	{	
		const size_t f_layerCount = a_animation->GetLayersSize();
		OpenKey(nullptr, KeyContainerType::Braces);

		OpenKey("animationdata", KeyContainerType::Braces);

		AddKeyValuePair<const FbxString>("name", a_animation->GetName(), true);
		AddKeyValuePair<const int>("layerCount", f_layerCount);
		AddKeyValuePair<const int>("animationLength", a_animation->GetFrameCount(), false, false);
		
		CloseKey(KeyContainerType::Braces, true);

		OpenKey("layers", KeyContainerType::Brackets);

		for (size_t i = 0; i < f_layerCount; ++i)
		{
			ExportAnimationLayer(a_animation->GetLayers()[i].get(), i != f_layerCount - 1);
		}

		CloseKey(KeyContainerType::Brackets);

		CloseKey(KeyContainerType::Braces, a_addComma);
	}

	void FbxJsonExporter::ExportAnimationLayer(const FbxAnimationLayer* a_layer, const bool a_addComma)
	{		
		const size_t f_nodeCount = a_layer->GetNodesSize();

		OpenKey(nullptr, KeyContainerType::Braces);

		AddKeyValuePair<const int>("nodeCount", f_nodeCount);

		OpenKey("nodes", KeyContainerType::Brackets);

		for (size_t i = 0; i < f_nodeCount; ++i)
		{
			ExportAnimationNode(a_layer->GetNodes()[i].get(), i != f_nodeCount - 1);
		}

		CloseKey(KeyContainerType::Brackets);

		CloseKey(KeyContainerType::Braces, a_addComma);
	}

	void FbxJsonExporter::ExportAnimationNode(const FbxAnimationNode* a_node, const bool a_addComma)
	{		
		const bool f_hasParent = a_node->HasParent();
		OpenKey(nullptr, KeyContainerType::Braces);

		OpenKey("nodedata", KeyContainerType::Braces);

		AddKeyValuePair<const int>("id", a_node->GetID() );
		AddKeyValuePair<const FbxString>("name", a_node->GetName(), true, f_hasParent);

		if (f_hasParent)
		{
			AddKeyValuePair<const int>("parentID", a_node->GetParentID());
			AddKeyValuePair<const FbxString>("parentName", a_node->GetParentName(), true, false);
		}

		CloseKey(KeyContainerType::Braces, true);

		OpenKey("channels", KeyContainerType::Braces);

		ExportAnimationChannels(a_node->GetTransform().get());		

		CloseKey(KeyContainerType::Braces);

		CloseKey(KeyContainerType::Braces, a_addComma);
	}

	void FbxJsonExporter::ExportAnimationChannels(const FbxAnimationTransform* a_transform)
	{
		const int f_transformCount = a_transform->GetTransformSize();	
		const int f_scaleSize = a_transform->GetScaleSize();

		ExportAnimationTransform(f_transformCount, "transform", a_transform->GetTransform(), FbxHelper::GetDQorMatrix4x4Length(), f_scaleSize > 0);
		//Check if scale exists
		if (f_scaleSize > 0)
		{
			ExportAnimationTransform(f_scaleSize, "scale", a_transform->GetScale(), 3, false);
		}		
	}

	void FbxJsonExporter::ExportAnimationTransform(const int a_keyCount, const char* a_keyName, const FbxAnimationTransformKey& a_keyValues, const int a_frameValueLength, const bool a_addComma)
	{
		OpenKey(a_keyName, KeyContainerType::Braces);

		AddKeyValuePair<const int>("keyCount", a_keyCount);
		OpenKey("keys", KeyContainerType::Brackets);
		const bool f_oldPrettify = m_prettify;
		int f_counter = 0;		
		if (f_oldPrettify)
		{
			AddNewLine();
			m_prettify = false;
		}		
		
		for (auto it = a_keyValues.begin(); it != a_keyValues.end(); ++it)
		{
			OpenKey(nullptr, KeyContainerType::Braces);

			AddKeyValuePair<const int>("frame", it->first);
			AddKeyValuePairArray<VectorConstDouble>("frameValue", it->second, a_frameValueLength, false);

			CloseKey(KeyContainerType::Braces, f_counter != a_keyCount - 1);

			if (f_counter % KEYFRAMES_PER_ROW == 0 && f_counter != 0)
			{
				if (f_oldPrettify)
				{
					m_prettify = true;
					AddNewLine();
					m_prettify = false;
				}
			}

			++f_counter;
		}

		m_prettify = f_oldPrettify;
		//Closes "keys"
		CloseKey(KeyContainerType::Brackets);
		//Closes "keyName"
		CloseKey(KeyContainerType::Braces, a_addComma);
	}

	void FbxJsonExporter::PrintOutput(std::wofstream& a_filestream)
	{		
		//Write m_output to file
		a_filestream << m_output;
		//Clear the output
		m_output.Clear();
	}

	void FbxJsonExporter::AddTabString()
	{
		if (!m_prettify) return;

		for (int i = 0; i < m_tabCount; ++i)
		{
			m_output += "\t";
		}		
	}

	void FbxJsonExporter::AddNewLine()
	{
		//If prettify is false then return since it's minimized
		if (!m_prettify) return;	
		
		m_output += "\n";
		AddTabString();		
	}	
	template<typename T>
	void FbxJsonExporter::AddStringValue(T a_value, const bool a_isKey)
	{
		m_output += "\"";
		m_output += a_value;
		m_output += "\"";
		//If the value is a key then add :
		if (a_isKey) m_output += ":";
	}
	/* NUMBER */
	template<typename T>
	void FbxJsonExporter::AddKeyValuePair(const char* a_key, const T a_value, const bool a_stringify, const bool a_addComma)
	{
		AddNewLine();
		//"key":
		AddStringValue<const char*>(a_key, true);
		
		if (a_stringify)
		{
			AddStringValue<T>(a_value);
		}
		else
		{
			//"key":value
			m_output += a_value;
		}
		
		//Add a comma 
		if (a_addComma)
		{
			m_output += ",";
		}
	}

	
	/// <summary>
	/// Adds a key value pair of an array/vector
	/// </summary>
	/// <param name="a_key">The key name</param>
	/// <param name="a_values">The array values</param>
	/// <param name="a_count">The size of array</param>
	/// <param name="a_addComma">If comma should be added</param>
	template<typename T>
	void FbxJsonExporter::AddKeyValuePairArray(const char* a_key, const T a_values, const int a_count, const bool a_addComma)
	{		
		OpenKey(a_key, KeyContainerType::Brackets);		
		AddNewLine();
		//Loop through a_values array
		for (int i = 0; i < a_count; ++i)
		{
			m_output += a_values[i];
			
			if (i != a_count - 1)
			{
				m_output += ",";
			}
			//If items per row has been exceeded then add a new line
			if (m_prettify && i % ITEMS_PER_ROW == 0 && i != 0)
			{
				AddNewLine();
			}
		}		

		CloseKey(KeyContainerType::Brackets, a_addComma);	
	}

	void FbxJsonExporter::OpenKey(const char* a_key, const KeyContainerType a_container)
	{
		
		AddNewLine();
		if (a_key != nullptr)
		{
			AddStringValue<const char*>(a_key, true);
			AddNewLine();
		}		
		if (a_container == KeyContainerType::Brackets)
		{
			m_output += "[";
		}
		else
		{
			m_output += "{";			
		}
		IncreaseTabCount();
	}
	
	/// <summary>
	/// Closes a group and decreases tabCount by 1
	/// </summary>
	/// <param name="a_containerType">Type of the a_container.</param>
	/// <param name="a_addComma">The a_add comma.</paramvoid	
	void FbxJsonExporter::CloseKey(const KeyContainerType a_container, const bool a_addComma)
	{
		DecreaseTabCount();
		AddNewLine();
		if (a_container == KeyContainerType::Brackets)
		{
			m_output += "]";
		}
		else
		{
			m_output += "}";
		}

		if (a_addComma)
		{
			m_output += ",";
		}
	}

	
	/// <summary>
	/// Increases the tab count by 1
	/// </summary>
	void FbxJsonExporter::IncreaseTabCount()
	{
		++m_tabCount;
	}
	/// <summary>
	/// Decreases the tab count by 1
	/// </summary>
	void FbxJsonExporter::DecreaseTabCount()
	{
		--m_tabCount;

		if (m_tabCount < 0)
		{
			#ifdef _DEBUG
				Cout << _T("Tab count decreased below 0") << std::endl;
			#endif 
			m_tabCount = 0;
		}
	}	
}